/*
 * Copyright 2004 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package edu.ubc.javascript;


import static edu.ubc.javascript.ASTFactory.name;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.mozilla.rhino.Parser;
import com.google.javascript.jscomp.mozilla.rhino.ast.ArrayLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.Assignment;
import com.google.javascript.jscomp.mozilla.rhino.ast.AstNode;
import com.google.javascript.jscomp.mozilla.rhino.ast.AstRoot;
import com.google.javascript.jscomp.mozilla.rhino.ast.Block;
import com.google.javascript.jscomp.mozilla.rhino.ast.BreakStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.CatchClause;
import com.google.javascript.jscomp.mozilla.rhino.ast.ConditionalExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.ContinueStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.DoLoop;
import com.google.javascript.jscomp.mozilla.rhino.ast.ElementGet;
import com.google.javascript.jscomp.mozilla.rhino.ast.EmptyExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.ExpressionStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.ForInLoop;
import com.google.javascript.jscomp.mozilla.rhino.ast.ForLoop;
import com.google.javascript.jscomp.mozilla.rhino.ast.FunctionCall;
import com.google.javascript.jscomp.mozilla.rhino.ast.FunctionNode;
import com.google.javascript.jscomp.mozilla.rhino.ast.IfStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.InfixExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.KeywordLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.Label;
import com.google.javascript.jscomp.mozilla.rhino.ast.LabeledStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.Name;
import com.google.javascript.jscomp.mozilla.rhino.ast.NewExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.NumberLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.ObjectLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.ObjectProperty;
import com.google.javascript.jscomp.mozilla.rhino.ast.ParenthesizedExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.PropertyGet;
import com.google.javascript.jscomp.mozilla.rhino.ast.RegExpLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.ReturnStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.StringLiteral;
import com.google.javascript.jscomp.mozilla.rhino.ast.SwitchCase;
import com.google.javascript.jscomp.mozilla.rhino.ast.SwitchStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.ThrowStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.TryStatement;
import com.google.javascript.jscomp.mozilla.rhino.ast.UnaryExpression;
import com.google.javascript.jscomp.mozilla.rhino.ast.VariableDeclaration;
import com.google.javascript.jscomp.mozilla.rhino.ast.VariableInitializer;
import com.google.javascript.jscomp.mozilla.rhino.ast.WhileLoop;
import com.google.javascript.jscomp.mozilla.rhino.ast.WithStatement;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.TokenStream;

/**
 * ASTFactory transforms IR to Rhino AST
 * Implementation is based on com.google.jscomp.CodeGenerator
 * 
 */
public class ASTFactory {
	
	public static boolean isAssignmentType(int type) {
		return (type == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN) ||
				(type == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_ADD) ||	
				(type == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITAND) ||
				(type == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITOR) ||
				(type == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITXOR) ||
				(type == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_DIV) || 
				(type == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_LSH) ||
				(type == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_MOD) ||
				(type == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_DIV) || 
				(type == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_RSH) ||
				(type == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_MUL) ||
				(type == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_SUB) || 
				(type == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_URSH);
	}
	
	public static boolean isAssign(AstNode node) {
		if(node instanceof Assignment) {
			return true;
		}
		if(node instanceof InfixExpression) {
			InfixExpression ie = (InfixExpression) node;
			if((ie.getOperator() == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN) ||
				(ie.getOperator() == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_ADD) ||	
				(ie.getOperator() == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITAND) ||
				(ie.getOperator() == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITOR) ||
				(ie.getOperator() == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_BITXOR) ||
				(ie.getOperator() == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_DIV) || 
				(ie.getOperator() == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_LSH) ||
				(ie.getOperator() == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_MOD) ||
				(ie.getOperator() == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_DIV) || 
				(ie.getOperator() == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_RSH) ||
				(ie.getOperator() == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_MUL) ||
				(ie.getOperator() == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_SUB) || 
				(ie.getOperator() == com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN_URSH)) {
				return true;
			}
		}
		return false;
	}
	
	public static VariableDeclaration var(String name) {
		VariableDeclaration vd = new VariableDeclaration();
		VariableInitializer vi = new VariableInitializer();
		vi.setTarget(name(name));
		vd.addVariable(vi);
		return vd;
	}
	
	public static UnaryExpression not(AstNode expr) {
		UnaryExpression ue = new UnaryExpression();
		ue.setIsPostfix(false);
		ue.setOperator(com.google.javascript.jscomp.mozilla.rhino.Token.NOT);
		ue.setOperand(expr);
		return ue;
	}
	
	public static InfixExpression subtract(AstNode left, AstNode right) {
		InfixExpression ie = new InfixExpression();
		ie.setOperator(com.google.javascript.jscomp.mozilla.rhino.Token.SUB);
		ie.setLeft(left);
		ie.setRight(right);
		return ie;
	}
	
	public static NewExpression newExpr(String type, AstNode ...args) {
		NewExpression newExpr = new NewExpression();
		newExpr.setTarget(name(type));
		newExpr.setArguments(Arrays.asList(args));
		return newExpr;
	}
	
	public static IfStatement ifThen(AstNode condition, AstNode thenPart) {
		IfStatement ifExpr = new IfStatement();
		ifExpr.setCondition(condition);
		ifExpr.setThenPart(thenPart);
		return ifExpr;		
	}
	
	public static IfStatement ifThenElse(AstNode condition, AstNode thenPart, AstNode elsePart) {
		IfStatement ifExpr = new IfStatement();
		ifExpr.setCondition(condition);
		ifExpr.setThenPart(thenPart);
		ifExpr.setElsePart(elsePart);
		return ifExpr;		
	}
	
	public static InfixExpression tripleEquals(AstNode left, AstNode right) {
		InfixExpression ie = new InfixExpression();
		ie.setOperator(com.google.javascript.jscomp.mozilla.rhino.Token.SHEQ);
		ie.setLeft(left);
		ie.setRight(right);
		return ie;
	}
	
	public static InfixExpression doubleNotEquals(AstNode left, AstNode right) {
		InfixExpression ie = new InfixExpression();
		ie.setOperator(com.google.javascript.jscomp.mozilla.rhino.Token.SHNE);
		ie.setLeft(left);
		ie.setRight(right);
		return ie;
	}
	
	public static InfixExpression and(AstNode left, AstNode right) {
		InfixExpression ie = new InfixExpression();
		ie.setOperator(com.google.javascript.jscomp.mozilla.rhino.Token.AND);
		ie.setLeft(left);
		ie.setRight(right);
		return ie;
	}
	
	public static InfixExpression or(AstNode left, AstNode right) {
		InfixExpression ie = new InfixExpression();
		ie.setOperator(com.google.javascript.jscomp.mozilla.rhino.Token.OR);
		ie.setLeft(left);
		ie.setRight(right);
		return ie;
	}
	
	public static ConditionalExpression conditional(AstNode test, AstNode trueExpr, AstNode falseExpr) {
		ConditionalExpression ce = new ConditionalExpression();
		ce.setTestExpression(test);
		ce.setTrueExpression(trueExpr);
		ce.setFalseExpression(falseExpr);
		return ce;
	}
	
	public static StringLiteral string(String value) {
		StringLiteral sl = new StringLiteral();
		sl.setValue(value);
		sl.setQuoteCharacter('"');
		return sl;
	}
	
	public static FunctionCall call(AstNode target, AstNode ... args) {
		FunctionCall call = new FunctionCall();
		call.setTarget(target);
		List<AstNode> actuals = new LinkedList<AstNode>();
		for(AstNode arg : args) {
			actuals.add(arg);
		}
		call.setArguments(actuals);
		return call;
	}
	
	public static FunctionCall call(String name, AstNode ... args) {
		return call(name(name), args);
	}
	
	public static ObjectProperty prop(AstNode target, AstNode value) {
		ObjectProperty op = new ObjectProperty();
		op.setLeft(target);
		op.setRight(value);
		return op;
	}
	
	public static ExpressionStatement exprStmt(AstNode expr) {
		ExpressionStatement stmt = new ExpressionStatement();
		stmt.setExpression(expr);
		return stmt;
	}
	
	public static Assignment assign() {
		Assignment assign = new Assignment();
		assign.setOperator(com.google.javascript.jscomp.mozilla.rhino.Token.ASSIGN);
		return assign;
	}
	
	public static Assignment assign(AstNode left, AstNode right) {
		Assignment assign = assign();
		assign.setLeft(left);
		assign.setRight(right);
		return assign;
	}
	
	public static PropertyGet get(AstNode target, String prop) {
		PropertyGet pg = new PropertyGet();
		pg.setTarget(target);
		pg.setProperty(name(prop));
		return pg;
	}
	
	public static ElementGet element(AstNode target, AstNode prop) {
		ElementGet pg = new ElementGet();
		pg.setTarget(target);
		pg.setElement(prop);
		return pg;
	}
	
	public static boolean isBlockOrRoot(AstNode node) {
		return (node instanceof Block) || (node instanceof AstRoot);
	}
	
	public static ParenthesizedExpression paren(AstNode expr) {
		ParenthesizedExpression pe = new ParenthesizedExpression();
		pe.setExpression(expr);
		return pe;
	}
	
	public static InfixExpression comma() {
		InfixExpression ie = new InfixExpression();
		ie.setOperator(com.google.javascript.jscomp.mozilla.rhino.Token.COMMA);
		return ie;
	}
	
	public static InfixExpression comma(AstNode left, AstNode right) {
		InfixExpression ie = comma();
		ie.setLeft(left);
		ie.setRight(right);
		return ie;
	}
	
	public static Name name(String id) {
		Name name = new Name();
		name.setIdentifier(id);
		return name;
	}
	
	public static AstNode commaList(List<AstNode> exprs) {
		int length = exprs.size();
		if(length == 0) {
			return new EmptyExpression();
		} else if(length == 1) {
			return exprs.get(0);
		} else {
			InfixExpression ie = comma();
			InfixExpression head = ie;
			
			for(int i=0; i < length; i++) {
				ie.setLeft(exprs.get(i));
				if(i == (length-2)) {
					ie.setRight(exprs.get(i+1));
					return head;
				} else {
					InfixExpression next = comma();
					ie.setRight(next);
					ie = next;
				}
			}
			throw new RuntimeException("This should not happen");
		}
	}
	
	public static AstRoot parse(String code) {
		Parser parser = new Parser();
		return parser.parse(code, "", 0);
	}
	
	public static <T> T parseStmt(Class<T> clazz, String code) {
		Parser parser = new Parser();
		return (T) parser.parse(code, "", 0).getFirstChild();
	}

	private Name addIdentifier(String identifier) {
		Name name = new Name();
		name.setString(identifier);
		return name;
	}

	public AstNode add(Node n) {
		return add(n, Context.OTHER);
	}

	AstNode add(Node n, Context context) {
		int type = n.getType();
		String opstr = NodeUtil.opToStr(type);
		int childCount = n.getChildCount();
		Node first = n.getFirstChild();
		Node last = n.getLastChild();

		// Handle all binary operators
		if (opstr != null && first != last) {
			int newType = convertType(type);
			InfixExpression ie;
			if(isAssignmentType(newType)) {
				ie = new Assignment();
			} else {
				ie = new InfixExpression();
			}
			Preconditions.checkState(childCount == 2);
			int p = NodeUtil.precedence(type);
			AstNode left = addLeftExpr(first, p, context);
			ie.setLeft(left);
			ie.setOperator(newType);

			// For right-hand-side of operations, only pass context if it's
			// the IN_FOR_INIT_CLAUSE one.
			Context rhsContext = getContextForNoInOperator(context);
			AstNode right;
			// Handle associativity.
			// e.g. if the parse tree is a * (b * c),
			// we can simply generate a * b * c.
			if (last.getType() == type && NodeUtil.isAssociative(type)) {
				right = addExpr(last, p, rhsContext);
				ie.setRight(right);
			} else if (NodeUtil.isAssignmentOp(n)
					&& NodeUtil.isAssignmentOp(last)) {
				// Assignments are the only right-associative binary operators
				right = addExpr(last, p, rhsContext);
				ie.setRight(right);
			} else {
				right = addExpr(last, p + 1, rhsContext);
				ie.setRight(right);
			}
			return ie;
		}

		switch (type) {
		case Token.TRY: {
			Preconditions.checkState(first.getNext().getType() == Token.BLOCK
					&& first.getNext().getChildCount() <= 1);
			Preconditions.checkState(childCount >= 2 && childCount <= 3);

			TryStatement tr = new TryStatement();

			AstNode tryBlock = add(first, Context.PRESERVE_BLOCK);
			tr.setTryBlock(tryBlock);
			// second child contains the catch block, or nothing if there
			// isn't a catch block
			Node catchblock = first.getNext().getFirstChild();
			if (catchblock != null) {
				CatchClause catchPart = (CatchClause) add(catchblock);
				List<CatchClause> lcc = new LinkedList<CatchClause>();
				lcc.add(catchPart);
				tr.setCatchClauses(lcc);
			}

			if (childCount == 3) {
				AstNode finallyBlock = add(last, Context.PRESERVE_BLOCK);
				tr.setFinallyBlock(finallyBlock);
			}
			return tr;
		}

		case Token.CATCH:
			Preconditions.checkState(childCount == 3);
			if (first.getNext().getType() != Token.EMPTY) {
				throw new Error("Catch conditions not suppored because I think"
						+ " that it may be a netscape only feature.");
			}
			CatchClause cc = new CatchClause();

			Name varName = (Name) add(first);
			cc.setVarName(varName);

			Block body = (Block) add(last, Context.PRESERVE_BLOCK);
			cc.setBody(body);
			return cc;

		case Token.THROW:
			Preconditions.checkState(childCount == 1);
			ThrowStatement ts = new ThrowStatement();
			AstNode expression = add(first);
			ts.setExpression(expression);
			return ts;

		case Token.RETURN:
			ReturnStatement rs = new ReturnStatement();

			if (childCount == 1) {
				AstNode returnValue = add(first);
				rs.setReturnValue(returnValue);
			} else {
				Preconditions.checkState(childCount == 0);
			}
			return rs;

		case Token.VAR:
			if (first != null) {
				VariableDeclaration vd = new VariableDeclaration();
				AstNode current = add(first);
				List<VariableInitializer> lvi = new LinkedList<VariableInitializer>();
				VariableInitializer vi = new VariableInitializer();
				if (current instanceof Assignment) {
					Assignment currentAssignment = (Assignment) current;
					vi.setTarget(currentAssignment.getLeft());
					vi.setInitializer(currentAssignment.getRight());
				} else {
					vi.setTarget(current);
				}
				lvi.add(vi);
				vd.setVariables(lvi);
				if(!(context == Context.IN_FOR_INIT_CLAUSE)) {
					ExpressionStatement expr = new ExpressionStatement();
					expr.setExpression(vd);
					return expr;
				} else {
					return vd;
				}
			}
			return new EmptyExpression();

		case Token.LABEL_NAME:
			Preconditions.checkState(!n.getString().isEmpty());
			return addIdentifier(n.getString());

		case Token.NAME:
			AstNode returnValue = null;
			if (first == null || first.getType() == Token.EMPTY) {
				returnValue = addIdentifier(n.getString());
				TagManager.mapAllTags(n, returnValue);
			} else {
				Preconditions.checkState(childCount == 1);
				Assignment assignment = new Assignment();
				Name left = addIdentifier(n.getString());
				assignment.setLeft(left);
				TagManager.mapAllTags(n, left);
				AstNode right;
				if (first.getType() == Token.COMMA) {
					right = addExpr(first, NodeUtil.precedence(Token.ASSIGN));
				} else {
					// Add expression, consider nearby code at lowest level of
					// precedence.
					right = addExpr(first, 0,
							getContextForNoInOperator(context));
				}
				assignment.setRight(right);
				returnValue = assignment;
			}
			return returnValue;
		case Token.ARRAYLIT:
			ArrayLiteral al = new ArrayLiteral();
			List<AstNode> list = addList(first, (int[]) n
					.getProp(Node.SKIP_INDEXES_PROP));
			al.setElements(list);
			return al;

		case Token.LP:
			ParenthesizedExpression pe = new ParenthesizedExpression();
			pe.setExpression(add(first));
			return pe;

		case Token.COMMA:
			InfixExpression infix = new InfixExpression();
			infix.setLeft(add(first));
			infix.setRight(add(first.getNext()));
			infix.setOperator(convertType(Token.COMMA));
			return infix;
		case Token.NUMBER:
			Preconditions.checkState(childCount == 0);
			NumberLiteral nl = new NumberLiteral();
			nl.setValue(Double.toString(n.getDouble()));
			nl.setDouble(n.getDouble());
			return nl;
		case Token.VOID:
			return add(first);
		case Token.TYPEOF:
		case Token.NOT:
		case Token.BITNOT:
		case Token.POS:
		case Token.NEG: {
			// All of these unary operators are right-associative
			Preconditions.checkState(childCount == 1);
			UnaryExpression ue = new UnaryExpression();
			AstNode expr = addExpr(first, NodeUtil.precedence(type));
			ue.setOperator(convertType(type));
			ue.setOperand(expr);
			return ue;
		}

		case Token.HOOK: {
			Preconditions.checkState(childCount == 3);
			ConditionalExpression ce = new ConditionalExpression();
			int p = NodeUtil.precedence(type);
			AstNode test = addLeftExpr(first, p + 1, context);
			ce.setTestExpression(test);
			AstNode trueExpr = addExpr(first.getNext(), p);
			ce.setTrueExpression(trueExpr);
			AstNode falseExpr = addExpr(last, p);
			ce.setFalseExpression(falseExpr);
			return ce;
		}

		case Token.REGEXP:
			if (first.getType() != Token.STRING
					|| last.getType() != Token.STRING) {
				throw new Error("Expected children to be strings");
			}
			RegExpLiteral rel = new RegExpLiteral();
			String regexp = first.getString();

			// I only use one .add because whitespace matters
			if (childCount == 2) {
				rel.setValue(regexp);
				rel.setFlags(last.getString());
			} else {
				Preconditions.checkState(childCount == 1);
				rel.setValue(regexp);
			}
			return rel;

		case Token.GET_REF:
			return add(first);

		case Token.REF_SPECIAL:
			Preconditions.checkState(childCount == 1);
			PropertyGet pg = new PropertyGet();
			AstNode target = add(first);
			pg.setTarget(target);
			pg.setProperty(addIdentifier((String) n.getProp(Node.NAME_PROP)));
			return pg;

		case Token.FUNCTION:
			if (n.getClass() != Node.class) {
				throw new Error("Unexpected Node subclass.");
			}
			Preconditions.checkState(childCount == 3);
			boolean funcNeedsParens = (context == Context.START_OF_EXPR);
			
			FunctionNode fn = new FunctionNode();
			if (funcNeedsParens) {
				pe = new ParenthesizedExpression();
				pe.setExpression(fn);
				returnValue = pe;
			} else {
				returnValue = fn;
			}

			fn.setFunctionName((Name) add(first));

			Node lp = first.getNext();
			List<AstNode> params = this.addList(lp.getFirstChild());
			fn.setParams(params);

			fn.setBody(add(last, Context.PRESERVE_BLOCK));
			return returnValue;

		case Token.SCRIPT:
			AstRoot root = new AstRoot();
			for (Node c = first; c != null; c = c.getNext()) {
				root.addChild(add(c, Context.STATEMENT));
			}
			return root;
		case Token.BLOCK: {
			if (n.getClass() != Node.class) {
				throw new Error("Unexpected Node subclass.");
			}
			boolean stripBlock = n.isSyntheticBlock()
					|| ((context != Context.PRESERVE_BLOCK) && (n
							.getChildCount() < 2));
			if(stripBlock) {
				return add(first);
			} else {
				Block block = new Block();

				for (Node c = first; c != null; c = c.getNext()) {
					block.addStatement(add(c, Context.STATEMENT));
				}
				return block;
			}
		}

		case Token.FOR:
			ForLoop fl = new ForLoop();
			if (childCount == 4) {

				if (first.getType() == Token.VAR) {
					fl.setInitializer(add(first, Context.IN_FOR_INIT_CLAUSE));
				} else {
					fl.setInitializer(addExpr(first, 0,
							Context.IN_FOR_INIT_CLAUSE));
				}

				fl.setCondition(add(first.getNext()));
				fl.setIncrement(add(first.getNext().getNext()));

				fl.setBody(addNonEmptyExpression(last,
						getContextForNonEmptyExpression(context), false));
				return fl;
			} else {
				Preconditions.checkState(childCount == 3);
				ForInLoop fil = new ForInLoop();

				fil.setIterator(add(first, Context.IN_FOR_INIT_CLAUSE));
				AstNode it = add(first.getNext());
				
				fil.setIteratedObject(it);
				fil.setBody(addNonEmptyExpression(last,
						getContextForNonEmptyExpression(context), false));

				return fil;
			}

		case Token.DO:
			if(childCount != 2) {
				Preconditions.checkState(childCount == 2);
			}
			DoLoop dl = new DoLoop();
			dl.setBody(addNonEmptyExpression(first, Context.OTHER, false));
			dl.setCondition(add(last));
			return dl;

		case Token.WHILE:
			Preconditions.checkState(childCount == 2);
			WhileLoop wl = new WhileLoop();
			wl.setCondition(add(first));
			wl.setBody(addNonEmptyExpression(last,
					getContextForNonEmptyExpression(context), false));
			return wl;

		case Token.EMPTY:
			Preconditions.checkState(childCount == 0);
			return new EmptyExpression();

		case Token.GETPROP:
			Preconditions.checkState(childCount == 2);
			Preconditions.checkState(last.getType() == Token.STRING);
			boolean needsParens = (first.getType() == Token.NUMBER);

			pg = new PropertyGet();
			returnValue = null;
			if (needsParens) {
				pe = new ParenthesizedExpression();
				pe.setExpression(pg);
				returnValue = pe;
			} else {
				returnValue = pg;
			}
			pg.setLeft(addLeftExpr(first, NodeUtil.precedence(type), context));
			pg.setProperty(addIdentifier(last.getString()));
			return pg;

		case Token.GETELEM:
			Preconditions.checkState(childCount == 2);
			ElementGet eg = new ElementGet();
			eg.setTarget(addLeftExpr(first, NodeUtil.precedence(type), context));
			eg.setElement(add(first.getNext()));
			return eg;

		case Token.WITH:
			Preconditions.checkState(childCount == 2);
			WithStatement ws = new WithStatement();
			ws.setExpression(add(first));
			ws.setStatement(addNonEmptyExpression(last,
					getContextForNonEmptyExpression(context), false));
			return ws;

		case Token.INC:
		case Token.DEC: {
			Preconditions.checkState(childCount == 1);
			UnaryExpression ue = new UnaryExpression();
			ue.setOperator(convertType(type));
			int postProp = n.getIntProp(Node.INCRDECR_PROP, 0);
			// A non-zero post-prop value indicates a post inc/dec, default of
			// zero
			// is a pre-inc/dec.
			if (postProp != 0) {
				ue.setOperand(addLeftExpr(first, NodeUtil.precedence(type),
						context));
				ue.setIsPostfix(true);
			} else {
				ue.setIsPostfix(false);
				ue.setOperand(add(first));
			}
			return ue;
		}

		case Token.CALL:
			// If the left hand side of the call is a direct reference to eval,
			// then it must have a DIRECT_EVAL annotation. If it does not, then
			// that means it was originally an indirect call to eval, and that
			// indirectness must be preserved.
			FunctionCall fc = new FunctionCall();
			
			if (first.getType() == Token.NAME
					&& "eval".equals(first.getString())
					&& !first.getBooleanProp(Node.DIRECT_EVAL)) {
				// add("(0,eval)");
				throw new UnsupportedOperationException(
						"CALL and !first.getBooleanProp(Node.DIRECT_EVAL)");
			} else {
				fc.setTarget(addLeftExpr(first, NodeUtil.precedence(type),
						context));
			}

			fc.setArguments(addList(first.getNext()));
			return fc;

		case Token.IF:
			boolean hasElse = childCount == 3;
			boolean ambiguousElseClause = context == Context.BEFORE_DANGLING_ELSE
					&& !hasElse;
			returnValue = null;

			IfStatement is = new IfStatement();
			if (ambiguousElseClause) {
				Block block = new Block();
				block.addStatement(is);
				returnValue = block;
			} else {
				returnValue = is;
			}

			is.setCondition(add(first));

			if (hasElse) {
				is.setThenPart(addNonEmptyExpression(first.getNext(),
						Context.BEFORE_DANGLING_ELSE, false));
				is.setElsePart(addNonEmptyExpression(last,
						getContextForNonEmptyExpression(context), false));
			} else {
				is.setThenPart(addNonEmptyExpression(first.getNext(),
						Context.OTHER, false));
				Preconditions.checkState(childCount == 2);
			}
			return returnValue;

		case Token.NULL:
		case Token.THIS:
		case Token.FALSE:
		case Token.TRUE:
			Preconditions.checkState(childCount == 0);
			KeywordLiteral kl = new KeywordLiteral();
			kl.setType(convertType(type));
			return kl;

		case Token.CONTINUE:
			Preconditions.checkState(childCount <= 1);
			ContinueStatement cs = new ContinueStatement();

			if (childCount == 1) {
				if (first.getType() != Token.LABEL_NAME) {
					throw new Error(
							"Unexpected token type. Should be LABEL_NAME.");
				}
				cs.setLabel((Name) add(first));
			}
			return cs;

		case Token.DEBUGGER:
			Preconditions.checkState(childCount == 0);
			kl = new KeywordLiteral();
			kl.setType(Token.DEBUGGER);
			return kl;

		case Token.BREAK:
			Preconditions.checkState(childCount <= 1);
			BreakStatement b = new BreakStatement();
			if (childCount == 1) {
				if (first.getType() != Token.LABEL_NAME) {
					throw new Error(
							"Unexpected token type. Should be LABEL_NAME.");
				}
				b.setBreakLabel((Name) add(first));
			}
			return b;

		case Token.EXPR_VOID:
		case Token.EXPR_RESULT:
			if (type == Token.EXPR_VOID) {
				throw new Error("Unexpected EXPR_VOID. Should be EXPR_RESULT.");
			}
			Preconditions.checkState(childCount == 1);
			ExpressionStatement expr = new ExpressionStatement();
			expr.setExpression(add(first, Context.START_OF_EXPR));
			return expr;

		case Token.NEW:
			NewExpression ne = new NewExpression();
			int precedence = NodeUtil.precedence(type);

			// If the first child contains a CALL, then claim higher precedence
			// to force parens. Otherwise, when parsed, NEW will bind to the
			// first viable parens
			if (NodeUtil.containsCall(first)) {
				precedence = NodeUtil.precedence(first.getType()) + 1;
			}
			ne.setTarget(addExpr(first, precedence));

			Node next = first.getNext();
			if (next != null) {
				ne.setArguments(addList(next));
			}
			return ne;

		case Token.STRING:
			Preconditions.checkState(childCount == 0);
			StringLiteral sl = new StringLiteral();
			sl.setQuoteCharacter('"');
			sl.setValue(n.getString());
			return sl;

		case Token.DELPROP:
			Preconditions.checkState(childCount == 1);
			UnaryExpression ue = new UnaryExpression();
			ue.setOperator(convertType(Token.DELPROP));
			ue.setOperand(add(first));
			return ue;

		case Token.OBJECTLIT: {
			Preconditions.checkState(childCount % 2 == 0);
			needsParens = (context == Context.START_OF_EXPR);
			returnValue = null;
			ObjectLiteral ol = new ObjectLiteral();
			if (needsParens) {
				pe = new ParenthesizedExpression();
				pe.setExpression(ol);
				returnValue = pe;
			} else {
				returnValue = ol;
			}

			for (Node c = first; c != null; c = c.getNext().getNext()) {
				ObjectProperty op = new ObjectProperty();
				// Object literal property names don't have to be quoted if they
				// are
				// not JavaScript keywords
				if (c.getType() == Token.STRING
						&& !TokenStream.isKeyword(c.getString())
						&& TokenStream.isJSIdentifier(c.getString()) &&
						// do not encode literally any non-literal characters
						// that were
						// unicode escaped.
						NodeUtil.isLatin(c.getString())) {

					op.setLeft(addIdentifier(c.getString()));
				} else {
					op.setLeft(addExpr(c, 1));
				}
				op.setRight(addExpr(c.getNext(), 1));
				ol.addElement(op);
			}

			return ol;
		}

		case Token.SWITCH:
			SwitchStatement ss = new SwitchStatement();
			ss.setExpression(add(first));
			ss.setCases(addAllSiblings(first.getNext()));
			return ss;

		case Token.CASE:
			Preconditions.checkState(childCount == 2);
			SwitchCase sc = new SwitchCase();
			sc.setExpression(add(first));
			Node current = last.getFirstChild();
			while(current != null) {
				sc.addStatement(add(current));
				current = current.getNext();
			}
			return sc;

		case Token.DEFAULT:
			Preconditions.checkState(childCount == 1);
			sc = new SwitchCase();
			current = first.getFirstChild();
			while(current != null) {
				sc.addStatement(add(current));
				current = current.getNext();
			}
			return sc;

		case Token.LABEL:
			Preconditions.checkState(childCount == 2);
			LabeledStatement label = new LabeledStatement();
			if (first.getType() != Token.LABEL_NAME) {
				throw new Error("Unexpected token type. Should be LABEL_NAME.");
			}
			List<Label> labels = new LinkedList<Label>();
			Label l = new Label();
			Name lname = (Name) add(first);
			l.setName(lname.getIdentifier());
			labels.add(l);
			label.setLabels(labels);
			label.setStatement(addNonEmptyExpression(last,
					getContextForNonEmptyExpression(context), true));
			return label;

			// This node is auto generated in anonymous functions and should
			// just get
			// ignored for our purposes.
		case Token.SETNAME:
			return new EmptyExpression();

		default:
			throw new Error("Unknown type " + type + "\n" + n.toStringTree());
		}

	}

	/**
	 * Adds a block or expression, substituting a VOID with an empty statement.
	 * This is used for "for (...);" and "if (...);" type statements.
	 * 
	 * @param n
	 *            The node to print.
	 * @param context
	 *            The context to determine how the node should be printed.
	 */
	private AstNode addNonEmptyExpression(Node n, Context context,
			boolean allowNonBlockChild) {
		Node nodeToProcess = n;

		if (!allowNonBlockChild && n.getType() != Token.BLOCK) {
			throw new Error("Missing BLOCK child.");
		}

		// Strip unneeded blocks, that is blocks with <2 children unless
		// the CodePrinter specifically wants to keep them.
		if (n.getType() == Token.BLOCK) {
			Block block = new Block();
			int count = getNonEmptyChildCount(n);
			if (count == 0) {
				return block;
			}

			if (count == 1) {
				// Hack around a couple of browser bugs:
				// Safari needs a block around function declarations.
				// IE6/7 needs a block around DOs.
				Node firstAndOnlyChild = getFirstNonEmptyChild(n);

				if (firstAndOnlyChild.getType() == Token.FUNCTION
						|| firstAndOnlyChild.getType() == Token.DO) {

					AstNode stmt = add(firstAndOnlyChild, Context.STATEMENT);
					block.addStatement(stmt);
					return block;
				} else {
					// Continue with the only child.
					nodeToProcess = firstAndOnlyChild;
				}
			}
		}

		if (nodeToProcess.getType() == Token.EMPTY) {
			// cc.endStatement(true);
			return new EmptyExpression();
		} else {
			return add(nodeToProcess, context);

			// VAR doesn't include ';' since it gets used in expressions - so
			// any
			// VAR in a statement context needs a call to endStatement() here.
			/*
			 * if (nodeToProcess.getType() == Token.VAR) { cc.endStatement(); }
			 */
		}
	}

	/**
	 * Adds a node at the left-hand side of an expression. Unlike
	 * {@link #addExpr(Node,int)}, this preserves information about the context.
	 * 
	 * The left side of an expression is special because in the JavaScript
	 * grammar, certain tokens may be parsed differently when they are at the
	 * beginning of a statement. For example, "{}" is parsed as a block, but
	 * "{'x': 'y'}" is parsed as an object literal.
	 */
	AstNode addLeftExpr(Node n, int minPrecedence, Context context) {
		return addExpr(n, minPrecedence, context);
	}

	AstNode addExpr(Node n, int minPrecedence) {
		return addExpr(n, minPrecedence, Context.OTHER);
	}

	private AstNode addExpr(Node n, int minPrecedence, Context context) {
		if ((NodeUtil.precedence(n.getType()) < minPrecedence)
				|| ((context == Context.IN_FOR_INIT_CLAUSE) && (n.getType() == Token.IN))) {

			ParenthesizedExpression pe = new ParenthesizedExpression();
			pe.setExpression(add(n, clearContextForNoInOperator(context)));
			return pe;
		} else {
			return add(n, context);
		}
	}

	List<AstNode> addList(Node firstInList) {
		return addList(firstInList, true, Context.OTHER);
	}

	List<AstNode> addList(Node firstInList, boolean isArrayOrFunctionArgument) {
		return addList(firstInList, isArrayOrFunctionArgument, Context.OTHER);
	}

	List<AstNode> addList(Node firstInList, boolean isArrayOrFunctionArgument,
			Context lhsContext) {
		List<AstNode> list = new LinkedList<AstNode>();
		for (Node n = firstInList; n != null; n = n.getNext()) {
			boolean isFirst = n == firstInList;
			AstNode add = null;
			if (isFirst) {
				add = addLeftExpr(n, isArrayOrFunctionArgument ? 1 : 0, lhsContext);
			} else {
				add = addExpr(n, isArrayOrFunctionArgument ? 1 : 0);
			}
			TagManager.mapAllTags(n, add);
			list.add(add);
		}
		return list;
	}

	/**
	 * This function adds a comma-separated list as is specified by an ARRAYLIT
	 * node with the associated skipIndexes array. This is a space optimization
	 * since we avoid creating a whole Node object for each empty array literal
	 * slot.
	 * 
	 * @param firstInList
	 *            The first in the node list (chained through the next
	 *            property).
	 * @param skipIndexes
	 *            If not null, then the array of skipped entries in the array.
	 */
	List<AstNode> addList(Node firstInList, int[] skipIndexes) {
		int nextSlot = 0;
		int nextSkipSlot = 0;
		List<AstNode> list = new LinkedList<AstNode>();
		for (Node n = firstInList; n != null; n = n.getNext()) {
			while (skipIndexes != null && nextSkipSlot < skipIndexes.length) {
				if (nextSlot == skipIndexes[nextSkipSlot]) {
					nextSlot++;
					nextSkipSlot++;
				} else {
					return list;
				}
			}
			list.add(addExpr(n, 1));
			nextSlot++;
		}
		return list;
	}

	AstNode addCaseBody(Node caseBody) {
		return add(caseBody);
	}

	List<SwitchCase> addAllSiblings(Node n) {
		List<SwitchCase> list = new LinkedList<SwitchCase>();
		for (Node c = n; c != null; c = c.getNext()) {
			list.add((SwitchCase) add(c));
		}
		return list;
	}

	
	/** Gets the number of children of this node that are non empty. */
	private static int getNonEmptyChildCount(Node n) {
		int i = 0;
		for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
			if (c.getType() != Token.EMPTY) {
				i++;
			}
		}
		return i;
	}

	/** Gets the first non-empty child of the given node. */
	private static Node getFirstNonEmptyChild(Node n) {
		for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
			if (c.getType() != Token.EMPTY) {
				return c;
			}
		}
		return null;
	}

	// Information on the current context. Used for disambiguating special
	// cases.
	// For example, a "{" could indicate the start of an object literal or a
	// block, depending on the current context.
	enum Context {
		STATEMENT, BEFORE_DANGLING_ELSE, // a hack to resolve the else-clause
		// ambiguity
		START_OF_EXPR, PRESERVE_BLOCK,
		// Are we inside the init clause of a for loop? If so, the containing
		// expression can't contain an in operator. Pass this context flag down
		// until we reach expressions which no longer have the limitation.
		IN_FOR_INIT_CLAUSE, OTHER
	}

	private Context getContextForNonEmptyExpression(Context currentContext) {
		return currentContext == Context.BEFORE_DANGLING_ELSE ? Context.BEFORE_DANGLING_ELSE
				: Context.OTHER;
	}

	/**
	 * If we're in a IN_FOR_INIT_CLAUSE, we can't permit in operators in the
	 * expression. Pass on the IN_FOR_INIT_CLAUSE flag through subexpressions.
	 */
	private Context getContextForNoInOperator(Context context) {
		return (context == Context.IN_FOR_INIT_CLAUSE ? Context.IN_FOR_INIT_CLAUSE
				: Context.OTHER);
	}

	/**
	 * If we're in a IN_FOR_INIT_CLAUSE, (and thus can't permit in operators in
	 * the expression), but have added parentheses, the expressions within the
	 * parens have no limits. Clear the context flag Be safe and don't clear the
	 * flag if it held another value.
	 */
	private Context clearContextForNoInOperator(Context context) {
		return (context == Context.IN_FOR_INIT_CLAUSE ? Context.OTHER : context);
	}
	
	private static int convertType(int type) {
		try {
			String name = com.google.javascript.rhino.Token.name(type);
			Field field = com.google.javascript.jscomp.mozilla.rhino.Token.class.getField(name);
			int val = (Integer) field.get(null);
			if(val == 92) {
				System.out.print("");
			}
			return val;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static FunctionCall defineProperty(AstNode target, String propName, AstNode val) {
		ObjectLiteral o2 = new ObjectLiteral();
		ObjectProperty value = ASTFactory.prop(ASTFactory.name("value"), val);
		ObjectProperty writable = ASTFactory.prop(ASTFactory.name("writable"), name("true"));
		ObjectProperty enumerable = ASTFactory.prop(ASTFactory.name("enumerable"), ASTFactory.name("false"));
		o2.addElement(value);
		o2.addElement(enumerable);
		o2.addElement(writable);
		return ASTFactory.call("Object.defineProperty", target, string(propName), o2);
	}
	
	public static VariableDeclaration var(String string, AstNode val) {
		VariableDeclaration vd = new VariableDeclaration();
		VariableInitializer vi = new VariableInitializer();
		vi.setTarget(name(string));
		vi.setInitializer(val);
		vd.addVariable(vi);
		return vd;
	}

}
